# -*- coding: utf-8 -*-

from hlm.base import ObjectDict
from hlm.port import PortIn, PortInDirect, PortOut
from hlm.lane import Lane, LanePortSender, LanePortReceiver
from hlm.channel import Channel, ChannelPortSender, ChannelPortReceiver


class Node:
    """
    The Node class provides a high-level abstraction of a hardware level module.
    It could be transferred into a Verilog Module or VHDL entity/architecture.

    For each node, it should have:
        - An `initialize` method to prepare all the resources.
        - A `tasks` variable to define all the methods which needed to run.
        - All the methods defines in `tasks`.
    """

    tasks = []

    def __init__(self, params: dict = {}):
        # Create the dictionary for registers and constants.
        self.port = ObjectDict()
        self.reg = ObjectDict()
        self.const = ObjectDict()
        self.node = ObjectDict()
        # Create the children node list.
        self.__children = []
        # Convert the params into an object dict.
        param_object_dict = ObjectDict()
        param_object_dict.__dict__ = params
        # Initial the node.
        self.initialize(param_object_dict)

    def initialize(self, params: ObjectDict) -> None:
        """ The node initialized method. All the initialization of the internal
        logic of the current node should be applied in this method.

        The parameter for customized node is passing by the `params` as an
        object. Accessing the attribute name could directly access the variable
        to be passed.
        """
        pass

    def port_in(self, max_size: int = -1, data_type: type = None) -> PortIn:
        """ Create a new PortIn instance for the node by providing its name.
        Args:
            max_size: The maximum queue holding size of the PortIn instance.
                      Default value is 0 which means unlimited.
            data_type: The data type hint to store, it is not useful.
        Returns: The PortIn instance.
        """
        # Generate the input port.
        port = PortIn()
        # Configure the maximum size of the port.
        port.set_max_size(max_size)
        # Give back the port.
        return port

    def port_in_direct(self, data_type: type = None) -> PortInDirect:
        """ Create a new PortInDirect instance for the node instance by
        providing its name.
        Args:
            data_type: The data type hint to store, it is not useful.
        Returns: The PortInDirect instance.
        """
        # Generate the direct input port.
        return PortInDirect()

    def port_out(self, data_type: type = None) -> PortOut:
        """ Create a new PortOut instance for the node by providing its name.
        Args:
            data_type: The data type hint to store, it is not useful.
        Returns: The PortOut instance.
        """
        # Generate the output port.
        return PortOut()

    def port_lane_receiver(self, lane_type: type = None) -> LanePortReceiver:
        """ Create a new Lane receiver port instance for the node by providing
        the type of lane and its name.

        Args:
            lane_type: The channel class instance.

        Returns: The lane receiver port instance.

        """
        assert issubclass(lane_type, Lane)
        # Generate the lane receiver port.
        return lane_type.get_receiver()

    def port_lane_sender(self, lane_type: type = None) -> LanePortSender:
        """ Create a new Lane sender port instance for the node by providing
        the type of lane and its name.

        Args:
            lane_type: The channel class instance.

        Returns: The lane sender port instance.

        """
        assert issubclass(lane_type, Lane)
        # Generate the lane sender port.
        return lane_type.get_sender()

    def port_channel_receiver(self, channel_type: type = None) -> ChannelPortReceiver:
        """ Create a new Channel receiver port instance for the node by
        providing the type of channel and its name.
        Args:
            channel_type: The channel class instance.
        Returns: The channel receiver port instance.
        """
        assert issubclass(channel_type, Channel)
        # Generate the channel receiver port.
        return channel_type.get_receiver()

    def port_channel_sender(self, channel_type: type = None) -> ChannelPortSender:
        """ Create a new Channel sender port instance for the node by providing
        the type of channel and its name.
        Args:
            name: The name of the ChannelPortSender instance.
            channel_type: The channel class instance.
        Returns: The channel sender port instance.
        """
        assert issubclass(channel_type, Channel)
        # Generate the channel sender port.
        return channel_type.get_sender()

    def port_bypass(self, raw_port):
        """ Bypass a port from internal node. This operation can be treated as
        exposing the internal port of a node to outside node.

        Args:
            name: The name of port.
            raw_port: The target internal ports.

        Returns: The bypass port instance.

        """
        # Give back the original port.
        return raw_port

    def node_create(self, node_type, params: dict = {}) -> 'Node':
        """ Create a child node of the current node instance.
        Args:
            name: The name of the child node instance.
            node_type: The class name of the child node instance.
            params: The parameters to initialize the child node.
        Returns: The created child node instance.
        """
        if not issubclass(node_type, Node):
            raise Exception(str(node_type) +
                            ' is not a Node type, cannot be created and '
                            'added as a child node')
        # Create a node, and add it to the list.
        node = node_type(params=params)
        # Add node to the list.
        self.__children.append(node)
        # Give back the node instance.
        return node

    def all_tasks(self) -> list:
        """ Get all the tasks for itself and all the child nodes.
        Returns: The task instance list.
        """
        # Get the tasks of itself.
        task_list = []
        # Gather all the task method in the list.
        for task_name in self.tasks:
            task_list.append(getattr(self, task_name))
        # Loop for all the child node of the current node.
        for child_node in self.__children:
            # Append the task list.
            task_list += child_node.all_tasks()
        # Give back the task list.
        return task_list


class TopModule(Node):
    """ TopModule is a wrapper around a parent node that encapsulates all nodes
    under simulation for a given session. A top module must exist in order for a
    session to be fired.

    A TopModule is a special type of Node which also supports all the features
    of the Node class. It adds several methods for support dump the top level
    signals.
    """
    def __init__(self):
        # Execute the parent initialization.
        super(TopModule, self).__init__({})
